package com.zgs.mall.seckill.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.zgs.common.utils.R;
import com.zgs.mall.seckill.feign.CouponFeign;
import com.zgs.mall.seckill.interceptor.SeckillUserLoginInterceptor;
import com.zgs.mall.seckill.service.SeckillService;
import com.zgs.mall.seckill.to.SecKillSkuRedisTo;
import com.zgs.mall.seckill.to.SeckillOrderTo;
import com.zgs.mall.seckill.vo.MemberRespVo;
import com.zgs.mall.seckill.vo.SecKillSessionVo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {
   // @Autowired
    CouponFeign couponFeign;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RabbitTemplate rabbitTemplate;
    final static  String SESSIONS_PREFIX="seckill:sessions:";
    final static  String SKUS_PREFIX="seckill:skus";
    final static  String SECKILL_STOCK="seckill:stock:";



    public List<SecKillSkuRedisTo> blockHandler() {
        return null;
    }

    //获取当前秒杀商品.
    //自定义限流方法上 注解形式 value 资源名   blockHandler被限制后调用哪个方法,要求返回值一样
    @SentinelResource(value = "getCurrentSeckillResource",blockHandler = "blockHandler")
    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckill() {
        long time = new Date().getTime();
        List<SecKillSkuRedisTo> collect =null  ;
        //自定义限流
        // 1.5.0 版本开始可以利用 try-with-resources 特性（使用有限制）
        // 资源名可使用任意有业务语义的字符串，比如方法名、接口名或其它可唯一标识的字符串。
        try(Entry entry = SphU.entry("seckillSkus")){
            Set<String> keys = stringRedisTemplate.keys(SESSIONS_PREFIX + "*");
            for (String key : keys) {
                String replace = key.replace(SESSIONS_PREFIX, "");
                String[] s = replace.split("_");
                Long startTime = Long.valueOf(s[0]);
                Long endTime = Long.valueOf(s[1]);
                if (startTime<=startTime && startTime<=endTime){
                    List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);
                    BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUS_PREFIX);
                    List<String> list = ops.multiGet(range);
                    if (list !=null){
                        collect = list.stream().map(item -> {
                            SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(item, SecKillSkuRedisTo.class);
                            return secKillSkuRedisTo;
                        }).collect(Collectors.toList());
                    }
                    break;
                }
            }
        }catch(BlockException e){

        }

        return collect;
    }

    /**
     * 秒杀
     * @param killId
     * @param key
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberRespVo memberRespVo = SeckillUserLoginInterceptor.userInfo.get();
        Long id = memberRespVo.getId();
        //1.获取秒杀的详细信息
        BoundHashOperations<String, String, String> boundHashOperations = stringRedisTemplate.boundHashOps(SKUS_PREFIX);
        String s = boundHashOperations.get(killId);
        if (StringUtils.isEmpty(s)){
            return null;
        }
        SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(s, SecKillSkuRedisTo.class);
        //校验合法性
        Long startTime = secKillSkuRedisTo.getStartTime();
        Long endTime = secKillSkuRedisTo.getEndTime();
        long time = new Date().getTime();
        long ttl =endTime-time;
        //校验时间的合法性
        if (time>=startTime && time<=endTime){
            String randomCode = secKillSkuRedisTo.getRandomCode();
            String skuId = secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId();
            if (killId.equals(skuId)&&randomCode.equals(key)){
                //验证购物数量是否合理
                if (num<secKillSkuRedisTo.getSeckillLimit().intValue()){
                    //是否已经购买过了,幂等性,只要秒杀成功就去占位,userId_sessionId_skuId
                        //setNx 不存在的时候才占位
                    String redisKey=id+"_"+skuId;
                    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(),ttl, TimeUnit.MILLISECONDS);
                    if(aBoolean){//占位成功
                        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_STOCK);
                        try {
                            boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                            //秒杀成功
                            //快速下单,发送Mq
                            String orderSn = IdWorker.getTimeId();
                            SeckillOrderTo orderTo =new SeckillOrderTo();
                            orderTo.setNum(num);
                            orderTo.setMemberId(id);
                            orderTo.setOrderSn(orderSn);
                            orderTo.setPromotionSessionId(secKillSkuRedisTo.getPromotionSessionId());
                            orderTo.setSkuId(secKillSkuRedisTo.getSkuId());
                            orderTo.setSeckillPrice(secKillSkuRedisTo.getSeckillPrice());
                            rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                            return orderSn;
                        } catch (InterruptedException e) {
                            return null;
                        }

                    }else{
                        return null;
                    }
                }else{
                    return null;
                }
            }else{
                return null;
            }
        }else{
            return null;
        }
    }

    //将当前3天的数据缓存到redis中
    @Override
    public void uploadSeckillSkuLatest30Days() {
        R r = couponFeign.getLates3DaySession();
        Integer code = r.getCode();
        if (code==0){
            List<SecKillSessionVo> data = r.getData(new TypeReference<List<SecKillSessionVo>>() {});
            //缓存到redis
            //1.缓存活动信息
            saveSessionInfos( data);
            //2.缓存活动的关联商品
            saveSessionSkuInfos(data);
        }
    }
    //1.缓存活动信息--该活动下的skuId
    private void saveSessionInfos(List<SecKillSessionVo> sessionVos){
        sessionVos.stream().forEach(session->{
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key=SESSIONS_PREFIX+startTime+"_"+endTime;
            Boolean hasKey = stringRedisTemplate.hasKey(key);
            if (!hasKey){
                List<String> collect = session.getSeckillSkuRelations().stream().map(sku -> {
                    String s = sku.getPromotionSessionId()+"_"+sku.getSkuId().toString();
                    return s;
                }).collect(Collectors.toList());
                stringRedisTemplate.opsForList().leftPushAll(key, collect);
            }
        });

    }
    //hash结构存到redis
    private void saveSessionSkuInfos(List<SecKillSessionVo> sessionVos){
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUS_PREFIX);
        sessionVos.stream().forEach(session->{
            session.getSeckillSkuRelations().stream().forEach(sku -> {
                Boolean hasKey = stringRedisTemplate.hasKey(sku.getPromotionSessionId() + "_" + sku.getSkuId());
                if (!hasKey){
                    SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo();
                    //sku基本信息 暂时不查
                    //SKU商品秒杀信息
                    BeanUtils.copyProperties(sku,secKillSkuRedisTo);

                    secKillSkuRedisTo.setStartTime(session.getStartTime().getTime());
                    secKillSkuRedisTo.setEndTime(session.getEndTime().getTime());
                    //随机码
                    String randomCode = UUID.randomUUID().toString().replace("-", "");
                    secKillSkuRedisTo.setRandomCode(randomCode);
                    //引入分布式的信号量
                    RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_STOCK + randomCode);
                    //商品秒杀的信号量
                    semaphore.trySetPermits(secKillSkuRedisTo.getSeckillCount().intValue());
                    String s1 = JSON.toJSONString(secKillSkuRedisTo);
                    ops.put(secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId(),s1);
                }

            });

        });

    }
}
